<?php

	/*
	 * Act_as_tree extension
	 */
	
	/*
	 * Usage in model:
	 * In the model class definition: public $implement = 'Db_Act_As_Tree';
	 * To extract parent children $Obj->list_children();
	 * To extract root elements $Obj->list_root_children()
	 */
	
	class Db_Act_As_Tree extends Phpr_Extension
	{
		private $_model_class;
		private $_model;
		private static $_object_cache = array();
		private static $_parent_cache = array();
		private static $_cache_sort_column = array();
		
		public $act_as_tree_parent_key = 'parent_id';
		public $act_as_tree_level = 0;
		public $act_as_tree_sql_filter = null;

		public function __construct($model)
		{
			$this->_model_class = get_class($model);
			$this->_model = $model;
		}
		
		public function list_children($orderBy = 'name')
		{
			if (!$this->cache_exists($orderBy))
				$this->init_cache($orderBy);

			$cache_key = $this->get_cache_key($orderBy);

			if (isset(self::$_object_cache[$this->_model_class][$cache_key][$this->_model->id]))
				return new Db_DataCollection(self::$_object_cache[$this->_model_class][$cache_key][$this->_model->id]);

			return new Db_DataCollection();
		}
		
		public function list_root_children($orderBy = 'name')
		{
			if (!$this->cache_exists($orderBy))
				$this->init_cache($orderBy);

			$cache_key = $this->get_cache_key($orderBy);

			if (isset(self::$_object_cache[$this->_model_class][$cache_key][-1]))
				return new Db_DataCollection(self::$_object_cache[$this->_model_class][$cache_key][-1]);

			return new Db_DataCollection();
		}
		
		public function list_all_children($orderBy = 'name')
		{
			$result = $this->list_all_children_recursive($orderBy);

			return $result;
		}

		public function list_all_children_recursive($orderBy)
		{
			$result = array();
			$children = $this->_model->list_children($orderBy);

			foreach ($children as $child)
			{
				$result[] = $child;

				$child_result = $child->list_all_children_recursive($orderBy);
				foreach ($child_result as $sub_child)
				 	$result[] = $sub_child;
			}
			
			return $result;
		}

		public function get_path($Separator = " > ", $IncludeThis = true, $orderBy = 'name')
		{
			if (!$this->cache_exists($orderBy))
				$this->init_cache($orderBy);

			$cache_key = $this->get_cache_key($orderBy);

			$Parts = array();
			
			if ($IncludeThis)
				$Parts[] = $this->_model->name;

			$Obj = $this->_model;
			$parentKey = $this->_model->act_as_tree_parent_key;
			while ($parent_id = $Obj->$parentKey)
			{
				$Parent = self::$_parent_cache[$this->_model_class][$cache_key][$parent_id];

				if (!$Parent)
					break;
					
				$Parts[] = $Parent->Name;
				$Obj = $Parent;
			}
			
			return implode($Separator, array_reverse($Parts));
		}
		
		public function get_parent($orderBy = 'name')
		{
			if (!$this->cache_exists($orderBy))
				$this->init_cache($orderBy);

			$cache_key = $this->get_cache_key($orderBy);
				
			$parentKey = $this->_model->act_as_tree_parent_key;
			if (!$this->_model->$parentKey)
				return null;
				
			if (!isset(self::$_parent_cache[$this->_model_class][$cache_key][$this->_model->$parentKey]))
				return null;
				
			return self::$_parent_cache[$this->_model_class][$cache_key][$this->_model->$parentKey];
		}
		
		public function get_parents($include_this = false, $orderBy = 'name')
		{
			$parent = $this->_model->get_parent($orderBy);
			$result = array();
			
			if ($include_this)
				$result[] = $this->_model;
			
			while ($parent != null)
			{
				$result[] = $parent;
				$parent = $parent->get_parent($orderBy);
			}
			
			return array_reverse($result);
		}
		
		private function init_cache($orderBy = 'name')
		{
			$class_name = $this->_model_class;
			$cache_key = $this->get_cache_key($orderBy);

			// $model = new $class_name();
			// $model->init_columns_info();

			$model = clone $this->_model;

			$model->order($orderBy);

			if ($model->act_as_tree_sql_filter)
				$model->where($model->act_as_tree_sql_filter);
				
			$records = $model->find_all();
			$_object_cache = array();
			$_parent_cache = array();

			$parentKey = $this->_model->act_as_tree_parent_key;
			foreach ($records as $record)
			{
				$parent_id = $record->$parentKey !== null ? $record->$parentKey : -1;
				
				if (!isset($_object_cache[$parent_id]))
					$_object_cache[$parent_id] = array();

				$_object_cache[$parent_id][] = $record;
				$_parent_cache[$record->id] = $record;
			}

			self::$_object_cache[$this->_model_class][$cache_key] = $_object_cache;
			self::$_parent_cache[$this->_model_class][$cache_key] = $_parent_cache;
			self::$_cache_sort_column[$this->_model_class][$cache_key] = $orderBy;
		}
		
		private function get_cache_key($orderBy)
		{
			return $orderBy.$this->_model->act_as_tree_sql_filter;
		}
		
		private function cache_exists($orderBy)
		{
			$cache_key = $this->get_cache_key($orderBy);
			return array_key_exists($this->_model_class, self::$_object_cache) && array_key_exists($cache_key, self::$_object_cache[$this->_model_class]);
		}
		
		private function cache_key_match($orderBy)
		{
			if (!array_key_exists($this->_model_class, self::$_cache_sort_column))
				return false;

			return self::$_cache_sort_column[$this->_model_class] == $orderBy;
		}
	}

?>